/*
 *  Copyright 2015-2016, RenGu Technology (BeiJing) Co., Ltd.
 *  All right reserved.
 *
 *  THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF RENGU TECHNOLOGY (BeiJing) CO., LTD.
 *
 *  THE CONTENTS OF THIS FILE MAY NOT BE DISCLOSED TO THIR PARTIES,
 *  COPIED OR DUPLICATED IN ANY FORM, IN WHOLE OR IN PART,
 *  WITHOUT THE PRIOR WRITTEN PERMISSION OF RENGU TECHNOLOGY (BeiJing) CO., LTD.
 *
 *  VirtAcctEstablishService
 *
 *  Edit History:
 *      lizhangtao Created on 2020年04月09日
 *
 */
package cc.rengu.igas.acct.core.service.trans;

import cc.rengu.igas.acct.common.constant.AcctCommonConstant;
import cc.rengu.igas.acct.common.dao.*;
import cc.rengu.igas.acct.common.dao.impl.AcctCustomerInfoMapperImpl;
import cc.rengu.igas.acct.common.dao.impl.AcctDayCutInfoMapperImpl;
import cc.rengu.igas.acct.common.dao.impl.AcctInfoMapperImpl;
import cc.rengu.igas.acct.common.dao.impl.AcctSequenceMapperImpl;
import cc.rengu.igas.acct.common.entity.AcctCustomerInfo;
import cc.rengu.igas.acct.common.entity.AcctDaycutInfo;
import cc.rengu.igas.acct.common.entity.AcctInfo;
import cc.rengu.igas.acct.common.enums.AcctCcyEnum;
import cc.rengu.igas.acct.common.enums.AcctRespCodeEnum;
import cc.rengu.igas.acct.common.enums.AcctStatusEnum;
import cc.rengu.igas.acct.facade.request.VirtAcctEstablishRequest;
import cc.rengu.igas.acct.facade.response.VirtAcctEstablishResponse;
import cc.rengu.oltp.service.base.RadpService;
import cc.rengu.oltp.service.common.constant.TreeNodeConstant;
import cc.rengu.oltp.service.common.enums.ChannelEnum;
import cc.rengu.oltp.service.common.enums.OltpRpcdEnum;
import cc.rengu.oltp.service.common.enums.TransStatusEnum;
import cc.rengu.oltp.service.model.BizException;
import cc.rengu.oltp.service.model.BizResponse;
import cc.rengu.oltp.utility.util.*;
import cc.rengu.utility.base.StringUtil;
import cc.rengu.utility.dbs.Database;
import com.alibaba.fastjson.JSONObject;

/**
 * 虚拟账户开户服务
 * 由于日切需要锁定所有账户，因此日切期间不能开户
 * 基于以上逻辑虚拟账户日切在批量时锁定了机构的日切信息表的数据该机构的所有数据，开户时也需要锁定
 *
 * @author lizhangtao
 * @version 1.0
 * @date 2020/5/8
 */
public class VirtAcctEstablishService extends RadpService {

    private String dateTimeMs = DateUtil.getCurrentDateTime(AcctCommonConstant.DATETIME_FORMAT_MS);
    private String dateTime14 = DateUtil.dateStringFormatConvert(dateTimeMs, AcctCommonConstant.DATETIME_FORMAT_MS,
            AcctCommonConstant.DATETIME_FORMAT_14);

    @Override
    protected int bizInit(String s) {
        return 0;
    }

    @Override
    protected int bizProcess(String s, int i) {
        XmlTreeUtil xmlTreeUtil = new XmlTreeUtil();
        try {
            VirtAcctEstablishRequest virtAcctEstablishRequest = new VirtAcctEstablishRequest();
            ConvertUtil.convertOutput(virtAcctEstablishRequest);
            rglog.info("virtAcctEstablishRequest = <{}>", JSONObject.toJSONString(virtAcctEstablishRequest));

            VirtAcctEstablishResponse virtAcctEstablishResponse = new VirtAcctEstablishResponse();
            virtAcctEstablishResponse.setHeader(virtAcctEstablishRequest.getHeader());
            BizResponse<VirtAcctEstablishResponse> bizResponse = new BizResponse<>();
            bizResponse.setResult(virtAcctEstablishResponse);

            //检查参数合法性
            String userType = virtAcctEstablishRequest.getUserType();
            String instId = virtAcctEstablishRequest.getHeader().getInstId();
            String srcSysId = virtAcctEstablishRequest.getHeader().getSrcSysId();
            String userId = virtAcctEstablishRequest.getUserId();

            if (StringUtil.isNullorEmpty(userType) || StringUtil.isNullorEmpty(instId) ||
                    StringUtil.isNullorEmpty(srcSysId) || StringUtil.isNullorEmpty(userId)) {
                rglog.error("<法人机构|用户类型|原系统编号|用户编号>不能为空");
                throw new BizException(AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespCode(),
                        AcctRespCodeEnum.MSG_VALID_CHECK_ERROR.getRespDesc());
            }

            String dbPoolName = MapperCommon.getDbPoolName();
            DbsUtil dbsUtil = new DbsUtil(dbPoolName);

            //锁定日切表
            AcctDayCutInfoMapper acctDayCutInfoMapper = new AcctDayCutInfoMapperImpl();

            AcctDaycutInfo acctDaycutInfo;
            acctDaycutInfo = acctDayCutInfoMapper.lockByInstId(virtAcctEstablishRequest.getHeader().getInstId());
            if (null == acctDaycutInfo) {
                throw new BizException(AcctRespCodeEnum.DAY_CUTTING_ERROR.getRespCode(),
                        AcctRespCodeEnum.DAY_CUTTING_ERROR.getRespDesc());
            }

            //处理原有可能开户成功或者容错部分成功
            BizResponse<VirtAcctEstablishResponse> bizResponseTmp = isExist(virtAcctEstablishRequest, acctDaycutInfo.getAcctDate(), dbsUtil);
            if (null == bizResponseTmp) {
                //首次开户
                BizResponse<VirtAcctEstablishResponse> bizResponseNew = createAcct(virtAcctEstablishRequest,
                        virtAcctEstablishResponse, acctDaycutInfo.getAcctDate(), dbsUtil);
                ConvertUtil.convertInput(bizResponseNew.getResult());
            } else {
                ConvertUtil.convertInput(bizResponseTmp.getResult());
            }
        } catch (BizException e) {
            try {
                xmlTreeUtil.setXmlTreeStringValue(TreeNodeConstant.RESP_CODE, e.getCode());
                xmlTreeUtil.setXmlTreeStringValue(TreeNodeConstant.RESP_DESC, e.getMessage());
            } catch (Exception e1) {
                rglog.error(StringUtil.ExceptionToString(e1));
            }
        } catch (Exception e) {
            rglog.error(StringUtil.ExceptionToString(e));
            try {
                xmlTreeUtil.setXmlTreeStringValue(TreeNodeConstant.RESP_CODE,
                        OltpRpcdEnum.BIZ_PROCESS_TIMEOUT.getRespCode());
                xmlTreeUtil.setXmlTreeStringValue(TreeNodeConstant.RESP_DESC,
                        OltpRpcdEnum.BIZ_PROCESS_TIMEOUT.getRespDesc());
            } catch (Exception e1) {
                rglog.error(StringUtil.ExceptionToString(e));
            }
        }
        return 0;
    }

    @Override
    protected int bizTerminate(String s) {
        return 0;
    }

    /**
     * 首次创建账户
     * 账户信息和客户信息入库作为一个原子操作
     * 虚拟账户编号 总32位长度 "user_type两位 + yyyyMMddHHmmss类型日期时间14位 + 10位顺序号 + 6位随机数"
     * 一个客户号对应一个虚拟账户号，客户号直接采用虚拟账户账户号
     *
     * @param virtAcctEstablishRequest  服务请求
     * @param virtAcctEstablishResponse 服务应答
     * @param dbsUtil                   数据库操作句柄
     * @return 返回业务信息
     * @throws BizException 其他失败抛出异常
     */
    private BizResponse<VirtAcctEstablishResponse> createAcct(VirtAcctEstablishRequest virtAcctEstablishRequest,
                                                              VirtAcctEstablishResponse virtAcctEstablishResponse, String acctDate,
                                                              DbsUtil dbsUtil) throws BizException {
        AcctSequenceMapper acctSequenceMapper = new AcctSequenceMapperImpl();
        String acctNoSeq;
        try {
            acctNoSeq = acctSequenceMapper.getNextAcctNoSequenceNo(dbsUtil);
        } catch (Exception e) {
            rglog.error(StringUtil.ExceptionToString(e));
            throw new BizException(AcctRespCodeEnum.DB_EXCEPTION.getRespCode(),
                    AcctRespCodeEnum.DB_EXCEPTION.getRespDesc());
        }
        //生成虚拟账户编号 总32位长度 = "3位法人行标识 + user_type两位 + yyyyMMddHHmmss类型日期时间14位 + 3位顺序号 + 6位随机数"
        //客户号：3位法人行标识 + 14位日期时间 + 3位顺序号 + 6位随机数
        String nonceStr = RandomUtil.randomNumStr(6);
        String acctNo = virtAcctEstablishRequest.getHeader().getInstId() + virtAcctEstablishRequest.getUserType() +
                dateTime14 + acctNoSeq + nonceStr;
        String customerId = virtAcctEstablishRequest.getHeader().getInstId() + dateTime14 + acctNoSeq + nonceStr;

        AcctCustomerInfo acctCustomerInfo = packAcctCustomerInfo(virtAcctEstablishRequest, customerId);
        AcctInfo acctInfo = packAcctInfo(virtAcctEstablishRequest, acctDate, acctNo, customerId);

        //开启事物
        dbsUtil.dbsBeginTransaction();
        //虚拟账户客户信息入库
        try {
            AcctCustomerInfoMapper acctCustomerInfoMapper = new AcctCustomerInfoMapperImpl();
            int retCode = acctCustomerInfoMapper.insert2AcctCustomerInfo(acctCustomerInfo, dbsUtil);
            if (Database.DBS_SUCCESS == retCode) {
                rglog.debug("登记客户信息成功！");
            } else {
                rglog.error("登记客户信息失败！");
                dbsUtil.dbsEndTransaction(false);
                throw new BizException(AcctRespCodeEnum.DB_ERROR.getRespCode(),
                        AcctRespCodeEnum.DB_ERROR.getRespDesc());
            }
        } catch (Exception e) {
            dbsUtil.dbsEndTransaction(false);
            throw new BizException(AcctRespCodeEnum.DB_EXCEPTION.getRespCode(),
                    AcctRespCodeEnum.DB_EXCEPTION.getRespDesc());
        }

        //虚拟账户信息入库
        try {
            AcctInfoMapper acctInfoMapper = new AcctInfoMapperImpl();
            int retCode = acctInfoMapper.insert2AcctInfo(acctInfo, dbsUtil);
            if (Database.DBS_SUCCESS == retCode) {
                rglog.debug("创建账户信息成功！");
            } else {
                rglog.warn("创建账户信息失败！");
                dbsUtil.dbsEndTransaction(false);
                throw new BizException(AcctRespCodeEnum.DB_ERROR.getRespCode(),
                        AcctRespCodeEnum.DB_ERROR.getRespDesc());
            }
        } catch (Exception e) {
            dbsUtil.dbsEndTransaction(false);
            throw new BizException(AcctRespCodeEnum.DB_EXCEPTION.getRespCode(),
                    AcctRespCodeEnum.DB_EXCEPTION.getRespDesc());
        }

        //成功结束事物
        dbsUtil.dbsEndTransaction(true);

        /* 组装应答报文 */
        virtAcctEstablishResponse.setRespCode(OltpRpcdEnum.TRANS_SUCCESS.getRespCode());
        virtAcctEstablishResponse.setRespDesc(OltpRpcdEnum.TRANS_SUCCESS.getRespDesc());
        virtAcctEstablishResponse.setCustomerId(acctCustomerInfo.getCustomerId());
        virtAcctEstablishResponse.setAcctNo(acctNo);
        BizResponse<VirtAcctEstablishResponse> bizResponse = new BizResponse<>();
        bizResponse.setRspSysId(ChannelEnum.ACCT.getChannelType());
        bizResponse.setRespCode(OltpRpcdEnum.TRANS_SUCCESS.getRespCode());
        bizResponse.setRespDesc(OltpRpcdEnum.TRANS_SUCCESS.getRespDesc());
        bizResponse.setResult(virtAcctEstablishResponse);
        bizResponse.setTransStatus(TransStatusEnum.TRANS_SUCC);
        return bizResponse;
    }

    /**
     * 判断是否存在，
     * 客户信息及账户信息同时存在则返回账户信息
     * 客户信息存在账户信息不存在则创建账户信息并返回
     * 客户信息及账户信息都不存在返回null
     * 其他失败抛出异常
     *
     * @param virtAcctEstablishRequest 服务请求
     * @param dbsUtil                  数据库操作句柄
     * @return 业务应答
     * @throws BizException 其他失败抛出业务异常
     */
    private BizResponse<VirtAcctEstablishResponse> isExist(VirtAcctEstablishRequest virtAcctEstablishRequest, String acctDate,
                                                           DbsUtil dbsUtil) throws BizException {
        VirtAcctEstablishResponse virtAcctEstablishResponse = new VirtAcctEstablishResponse();
        virtAcctEstablishResponse.setHeader(virtAcctEstablishRequest.getHeader());
        //先以法人机构+外部客户类型+用户标识查询客户是否存在
        AcctCustomerInfoMapper acctCustomerInfoMapper = new AcctCustomerInfoMapperImpl();
        try {
            AcctCustomerInfo acctCustomerInfo = acctCustomerInfoMapper.selectAcctCustomInfoByUserId(
                    virtAcctEstablishRequest.getHeader().getInstId(), virtAcctEstablishRequest.getUserType(),
                    virtAcctEstablishRequest.getUserId(), dbsUtil);
            //客户信息存在
            if (null != acctCustomerInfo) {
                String customerId = acctCustomerInfo.getCustomerId();
                //存在则以客户信息中查询到的客户号+法人机构号查询账户信息是否存在
                AcctInfoMapper acctInfoMapper = new AcctInfoMapperImpl();
                AcctInfo acctInfo = acctInfoMapper.selectAcctInfoByCustomerId(
                        virtAcctEstablishRequest.getHeader().getInstId(), customerId, dbsUtil);
                if (null != acctInfo) {
                    rglog.info("机构<{}>客户<{}>已经存在账号<{}>", virtAcctEstablishRequest.getHeader().getInstId(),
                            customerId, acctInfo.getAcctNo());
                    virtAcctEstablishResponse.setAcctNo(acctInfo.getAcctNo());
                    virtAcctEstablishResponse.setCustomerId(customerId);
                    //已经存在直接应答
                    BizResponse<VirtAcctEstablishResponse> bizResponse = new BizResponse<>();
                    bizResponse.setRspSysId(ChannelEnum.ACCT.getChannelType());
                    virtAcctEstablishResponse.setRespCode(OltpRpcdEnum.TRANS_SUCCESS.getRespCode());
                    virtAcctEstablishResponse.setRespCode(OltpRpcdEnum.TRANS_SUCCESS.getRespDesc());
                    bizResponse.setRespCode(OltpRpcdEnum.TRANS_SUCCESS.getRespCode());
                    bizResponse.setRespDesc(OltpRpcdEnum.TRANS_SUCCESS.getRespDesc());
                    bizResponse.setTransStatus(TransStatusEnum.TRANS_SUCC);
                    bizResponse.setResult(virtAcctEstablishResponse);
                    return bizResponse;
                } else {
                    //该情况为了容错，创建时已经开启事物因此不会出现客户信息已经插入而账户信息未插入的情况
                    String acctNo = customerId.substring(0, 3) + virtAcctEstablishRequest.getAcctType() +
                            customerId.substring(3);
                    AcctInfo acctInfoIn = packAcctInfo(virtAcctEstablishRequest, acctDate, acctNo, customerId);
                    if (Database.DBS_SUCCESS == acctInfoMapper.insert2AcctInfoAndCommit(acctInfoIn, dbsUtil)) {
                        //插入成功
                        virtAcctEstablishResponse.setAcctNo(acctInfoIn.getAcctNo());
                        virtAcctEstablishResponse.setCustomerId(customerId);
                        //已经存在直接应答
                        BizResponse<VirtAcctEstablishResponse> bizResponse = new BizResponse<>();
                        bizResponse.setRspSysId(ChannelEnum.ACCT.getChannelType());
                        virtAcctEstablishResponse.setRespCode(OltpRpcdEnum.TRANS_SUCCESS.getRespCode());
                        virtAcctEstablishResponse.setRespCode(OltpRpcdEnum.TRANS_SUCCESS.getRespDesc());
                        bizResponse.setRespCode(OltpRpcdEnum.TRANS_SUCCESS.getRespCode());
                        bizResponse.setRespDesc(OltpRpcdEnum.TRANS_SUCCESS.getRespDesc());
                        bizResponse.setTransStatus(TransStatusEnum.TRANS_SUCC);
                        bizResponse.setResult(virtAcctEstablishResponse);
                        return bizResponse;
                    } else {
                        //插入失败
                        throw new BizException(AcctRespCodeEnum.DB_ERROR.getRespCode(),
                                AcctRespCodeEnum.DB_ERROR.getRespDesc());
                    }
                }
            }
            //客户信息不存在则账户信息一定不存在即为首次创建的情况
        } catch (Exception e) {
            rglog.error(StringUtil.ExceptionToString(e));
            throw new BizException(AcctRespCodeEnum.DB_ERROR.getRespCode(), AcctRespCodeEnum.DB_ERROR.getRespDesc());
        }
        return null;
    }

    private AcctCustomerInfo packAcctCustomerInfo(VirtAcctEstablishRequest virtAcctEstablishRequest, String customerId) {
        AcctCustomerInfo acctCustomerInfo = new AcctCustomerInfo();
        acctCustomerInfo.setInstId(virtAcctEstablishRequest.getHeader().getInstId());
        acctCustomerInfo.setCustomerId(customerId);
        acctCustomerInfo.setCustomerName(virtAcctEstablishRequest.getCustomerName());
        acctCustomerInfo.setCertType(virtAcctEstablishRequest.getCertType());
        acctCustomerInfo.setCertNo(virtAcctEstablishRequest.getCertNo());
        acctCustomerInfo.setMobilePhone(virtAcctEstablishRequest.getMobilePhone());
        acctCustomerInfo.setUserType(virtAcctEstablishRequest.getUserType());
        acctCustomerInfo.setUserId(virtAcctEstablishRequest.getUserId());
        acctCustomerInfo.setUserStatus(AcctStatusEnum.NORMAL.getAcctStatus());
        acctCustomerInfo.setOprId(virtAcctEstablishRequest.getHeader().getSrcSysId());
        if (!StringUtil.isNullorEmpty(virtAcctEstablishRequest.getBirthday())) {
            acctCustomerInfo.setBirthday(virtAcctEstablishRequest.getBirthday());
        }
        acctCustomerInfo.setCreateTime(dateTimeMs);
        if (!StringUtil.isNullorEmpty(virtAcctEstablishRequest.getEmail())) {
            acctCustomerInfo.setEmail(virtAcctEstablishRequest.getEmail());
        }
        acctCustomerInfo.setLastOprId(virtAcctEstablishRequest.getHeader().getSrcSysId());
        acctCustomerInfo.setLastUpdateTime(dateTimeMs);
        if (!StringUtil.isNullorEmpty(virtAcctEstablishRequest.getSex())) {
            acctCustomerInfo.setSex(virtAcctEstablishRequest.getSex());
        }

        return acctCustomerInfo;
    }

    private AcctInfo packAcctInfo(VirtAcctEstablishRequest virtAcctEstablishRequest, String acctDate, String acctNo, String customerId) {

        AcctInfo acctInfo = new AcctInfo();
        acctInfo.setInstId(virtAcctEstablishRequest.getHeader().getInstId());
        acctInfo.setAcctNo(acctNo);
        acctInfo.setAcctType(AcctCommonConstant.DEFAULT_ACCT_TYPE);
        acctInfo.setCustomerId(customerId);
        acctInfo.setUserType(virtAcctEstablishRequest.getUserType());
        acctInfo.setUserId(virtAcctEstablishRequest.getUserId());
        acctInfo.setAcctCcy(AcctCcyEnum.RMB.getSrcCcy());
        acctInfo.setAcctBalance(AcctCommonConstant.ZERO_AMT);
        acctInfo.setAvailableBalance(AcctCommonConstant.ZERO_AMT);
        acctInfo.setFreezeBalance(AcctCommonConstant.ZERO_AMT);
        acctInfo.setYesterdayBalance(AcctCommonConstant.ZERO_AMT);
        acctInfo.setAcctOpenDate(acctDate);
        acctInfo.setAcctExpiryDate("");
        acctInfo.setAcctCancelDate("");
        acctInfo.setAcctStatus(AcctStatusEnum.NORMAL.getAcctStatus());
        acctInfo.setAcctDag(AcctCommonConstant.DEFAULT_DAG);
        acctInfo.setBalanceUpdateDate(acctDate);
        acctInfo.setLastTransDate(acctDate);
        acctInfo.setOprId(virtAcctEstablishRequest.getHeader().getSrcSysId());
        acctInfo.setCreateTime(dateTimeMs);
        acctInfo.setLastOprId(virtAcctEstablishRequest.getHeader().getSrcSysId());
        acctInfo.setLastUpdateTime(dateTimeMs);

        return acctInfo;
    }
}
